package org.hawk.gwt.ppc.reflect;

import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.hawk.gwt.ppc.reflect.lang.ClassDelegate;
import org.hawk.gwt.ppc.scanner.Token;
import org.hawk.gwt.ppc.utils.Name;

/**
 * Utility class for ClassDelegate subclasses generation.
 * Contains all the methods necessary for creating correct 
 * implementation of ClassDelegate to handle all the reflection
 * requests.
 * @author alex.bereznevatiy@gmail.com
 */
final class ClassDelegateBuilder {
	private final static Map<String,String> WRAPPERS;
	static {
		Map<String,String> m = new HashMap<String,String>();
		m.put(Boolean.TYPE.getName(), Boolean.class.getName());
		m.put(Byte.TYPE.getName(), Byte.class.getName());
		m.put(Double.TYPE.getName(), Double.class.getName());
		m.put(Float.TYPE.getName(), Float.class.getName());
		m.put(Integer.TYPE.getName(), Integer.class.getName());
		m.put(Long.TYPE.getName(), Long.class.getName());
		m.put(Short.TYPE.getName(), Short.class.getName());
		m.put(Character.TYPE.getName(), Character.class.getName());
		WRAPPERS = Collections.unmodifiableMap(m);
	}
	
	private String classSimpleName;
	private String className;
	private MetadataProcessor processor;
	private StringBuilder result;
	
	private ClassDelegateBuilder(String className,MetadataProcessor processor){
		this.className = className;
		this.classSimpleName = Name.getSimpleName(className);
		this.processor = processor;
	}
	/**
	 * Creates definition of the ClassDelegateBuilder and initiates 
	 * the building of ClassDelegate subclass for the className specified.
	 * @param className
	 * @param processor
	 * @return ClassDelegateBuilder to perform any additional work
	 */
	static ClassDelegateBuilder build(String className,MetadataProcessor processor){
		return new ClassDelegateBuilder(className,processor);
	}
	/**
	 * Writes the class metadata as {@link ClassDelegate}
	 * subclass to the last token of the compilation unit.
	 * @param lastToken
	 */
	void write(Token lastToken){
		StringBuilder result = new StringBuilder(100);
		result.append(this);
		result.append(lastToken.getRawToken());
		lastToken.setToken(result.toString());
	}

	public String toString() {
		if(result==null){
			result = new StringBuilder();
			buildClass();
		}
		return result.toString();
	}
	
	private void buildClass() {
		indent();
		header();
		method(JavaModifier.PROTECTED,"Class<?>[]","getImplementedInterfaces",
					getImplementedInterfaces());
		method(JavaModifier.PROTECTED,"String[]","getConstructorSignatures",
				getConstructorSignatures());
		method(JavaModifier.PROTECTED,"String[]","getFieldsSignatures",
				getFieldsSignatures());
		method(JavaModifier.PROTECTED,"String[]","getMethodSignatures",
				getMethodSignatures());
		method(JavaModifier.PUBLIC,"int","getModifiers",
				getModifiers(),"Object accessor","String signature");
		method(JavaModifier.PUBLIC,classSimpleName,"create",
				create(),"Object accessor",
				"String signature", "Object[] initargs");
		method(JavaModifier.PUBLIC,"Class<?>","getFieldType",
				getFieldType(),"Object accessor","String name");
		method(JavaModifier.PUBLIC,"Object","get",get(),
				"Object accessor","String name", "Object obj");
		method(JavaModifier.PUBLIC,"void","set",set(),
				"Object accessor","String name", "Object obj","Object value");
		method(JavaModifier.PUBLIC,"Class<?>","getReturnType",
				getReturnType(),"Object accessor","String signature");
		method(JavaModifier.PUBLIC,"Object","invoke",
				invoke(),"Object accessor","String signature",
				"Object obj", "Object[] parameters");
		result.append("\t};\n");
	}
	
	private List<String> getImplementedInterfaces() {
		List<String> result = new LinkedList<String>();
		result.add("return new Class[]{");
		Iterator<String>  interfaces = processor.getImplementedInterfaces(className).iterator();
		while(interfaces.hasNext()){
			String iFace = interfaces.next();
			line(result,"\t",iFace,interfaces.hasNext()?".class,":".class");
		}
		result.add("};");
		return result;
	}

	private List<String> getConstructorSignatures() {
		List<String> result = new LinkedList<String>();
		result.add("return new String[]{");
		Iterator<String>  constructors = processor.getDeclaredConstructors(className).iterator();
		if(!constructors.hasNext()&&!processor.isInterface(className)){
			line(result,"\t\"",Name.getSimpleName(className),"()\"");
		}
		while(constructors.hasNext()){
			String c = constructors.next();
			line(result,"\t\"",c,constructors.hasNext()?"\",":"\"");
		}
		result.add("};");
		return result;
	}
	
	private List<String> getFieldsSignatures() {
		List<String> result = new LinkedList<String>();
		result.add("return new String[]{");
		Iterator<String>  fields = processor.getDeclaredFields(className).iterator();
		while(fields.hasNext()){
			String fld = fields.next();
			int i = fld.indexOf(' ');
			if(i<0)continue;
			fld = fld.substring(i+1);
			line(result,"\t\"",fld,fields.hasNext()?"\",":"\"");
		}
		result.add("};");
		return result;
	}
	
	private List<String> getMethodSignatures() {
		List<String> result = new LinkedList<String>();
		result.add("return new String[]{");
		Iterator<String>  methods = processor.getDeclaredMethods(className).iterator();
		while(methods.hasNext()){
			String m = methods.next();
			int i = m.indexOf(' ');
			if(i>=0)m = m.substring(i+1);
			line(result,"\t\"",m,methods.hasNext()?"\",":"\"");
		}
		result.add("};");
		return result;
	}
	
	private List<String> getModifiers() {
		List<String> result = new LinkedList<String>();
		result.add("int sign = 0;");
		Map<String,Integer>  signatures = getAllSignatures();
		for(String signature : signatures.keySet()){
			if(result.size()==1){
				line(result,"if(signature.equals(\"",signature,
					"\"))sign|=",signatures.get(signature).toString(),";");
			}
			else {
				line(result,"else if(signature.equals(\"",signature,
					"\"))sign|=",signatures.get(signature).toString(),";");
			}
		}
		result.add("return super.getModifiers(accessor, signature)|sign;");
		return result;
	}
	
	private Map<String,Integer> getAllSignatures() {
		Map<String,Integer> result = new TreeMap<String, Integer>();
		List<String>  constructors = processor.getDeclaredConstructors(className);
		if(constructors.isEmpty()&&!processor.isInterface(className)){
			result.put(classSimpleName+"()",Modifier.PUBLIC);
		}
		for(String c : constructors){
			result.put(c,JavaModifier.getMask(
				processor.getModifier(className, c)));
		}
		List<String> fields = processor.getDeclaredFields(className);
		for(String fld : fields){
			fld = fld.substring(fld.indexOf(' ')+1);
			result.put(fld,JavaModifier.getMask(
				processor.getModifier(className, fld)));
		}
		List<String> methods = processor.getDeclaredMethods(className);
		for(String m : methods){
			m = m.substring(m.indexOf(' ')+1);
			result.put(m,JavaModifier.getMask(
				processor.getModifier(className, m)));
		}
		result.put("", JavaModifier.getMask(
			processor.getModifier(className, "")));
		return result;
	}
	private List<String> create() {
		List<String> result = new LinkedList<String>();
		result.add("super.create(accessor, signature, initargs);");
		Iterator<String>  signatures = processor.getDeclaredConstructors(className).iterator();
		if(!signatures.hasNext()&&!processor.isInterface(className)){
			line(result,"if(signature.equals(\"",classSimpleName,
				"()\"))return new ",classSimpleName,"();");
		}
		while(signatures.hasNext()){
			String signature = signatures.next();
			line(result,"if(signature.equals(\"",signature,
				"\"))return new ",prepareMethodCall(signature,"initargs"),";");
		}
		result.add("return null;");
		return result;
	}
	
	private String prepareMethodCall(String signature, String argsParam) {
		StringBuilder result = new StringBuilder();
		int start = signature.indexOf('(')+1;
		int end = signature.indexOf(')');
		result.append(signature,0,start);
		if(start<end){
			int coma = start;
			int arg = start;
			int i=0;
			while(coma<end){
				if(coma>start){
					result.append(',');
				}
				coma = signature.indexOf(',', arg);
				if(coma<0)coma = end;
				result.append('(');
				String type = signature.substring(arg, coma);
				if(WRAPPERS.containsKey(type)){
					type = WRAPPERS.get(type);
				}
				result.append(type);
				result.append(')');
				result.append(argsParam);
				result.append('[');
				result.append(i++);
				result.append(']');
				arg = coma+1;
			}
		}
		result.append(signature,end,signature.length());
		return result.toString();
	}
	private List<String> getFieldType() {
		List<String> result = new LinkedList<String>();
		result.add("super.getFieldType(accessor, name);");
		Iterator<String>  signatures = processor.getDeclaredFields(className).iterator();
		while(signatures.hasNext()){
			String signature = signatures.next();
			int i = signature.indexOf(' ');
			if(i<0){
				continue;
			}
			String name = signature.substring(i+1);
			String typeName = signature.substring(0,i);
			line(result,"if(name.equals(\"",name,
					"\"))return ",typeName,".class;");
		}
		result.add("return null;");
		return result;
	}
	private List<String> get() {
		List<String> result = new LinkedList<String>();
		result.add("super.get(accessor, name, obj);");
		Iterator<String>  signatures = processor.getDeclaredFields(className).iterator();
		while(signatures.hasNext()){
			String signature = signatures.next();
			int i = signature.indexOf(' ');
			if(i<0){
				continue;
			}
			String name = signature.substring(i+1);
			int mod = JavaModifier.getMask(processor.getModifier(className, name));
			StringBuilder obj = new StringBuilder();
			if(!Modifier.isStatic(mod)){
				obj.append("((");
				obj.append(className);
				obj.append(")obj).");
			}
			line(result,"if(name.equals(\"",name,
					"\"))return ",obj.toString(),name,";");
		}
		result.add("return null;");
		return result;
	}
	private List<String> set() {
		List<String> result = new LinkedList<String>();
		result.add("super.set(accessor, name, obj, value);");
		Iterator<String>  signatures = processor.getDeclaredFields(className).iterator();
		while(signatures.hasNext()){
			String signature = signatures.next();
			int i = signature.indexOf(' ');
			if(i<0){
				continue;
			}
			String name = signature.substring(i+1);
			String typeName = signature.substring(0,i);
			if(WRAPPERS.containsKey(typeName)){
				typeName = WRAPPERS.get(typeName);
			}
			int mod = JavaModifier.getMask(processor.getModifier(className, name));
			if(Modifier.isFinal(mod))continue;
			StringBuilder obj = new StringBuilder();
			if(!Modifier.isStatic(mod)){
				obj.append("((");
				obj.append(classSimpleName);
				obj.append(")obj).");
			}
			if(result.size()==1){
				line(result,"if(name.equals(\"",name,
					"\"))",obj.toString(),name,"=(",typeName,")value;");
			}
			else {
				line(result,"else if(name.equals(\"",name,
					"\"))",obj.toString(),name,"=(",typeName,")value;");
			}
		}
		return result;
	}
	private List<String> getReturnType() {
		List<String> result = new LinkedList<String>();
		result.add("super.getReturnType(accessor, signature);");
		Iterator<String>  methods = processor.getDeclaredMethods(className).iterator();
		while(methods.hasNext()){
			String m = methods.next();
			int i = m.indexOf(' ');
			if(i<0)continue;
			String signature = m.substring(i+1);
			String returnType = m.substring(0,i);
			line(result,"if(signature.equals(\"",signature,
				"\"))return ",returnType,".class;");
		}
		result.add("return null;");
		return result;
	}
	private List<String> invoke() {
		List<String> result = new LinkedList<String>();
		result.add("super.invoke(accessor, signature, obj, parameters);");
		Iterator<String>  methods = processor.getDeclaredMethods(className).iterator();
		while(methods.hasNext()){
			String m = methods.next();
			int i = m.indexOf(' ');
			if(i<0)continue;
			String signature = m.substring(i+1);
			String returnType = m.substring(0,i);
			int mod = JavaModifier.getMask(processor.getModifier(className, signature));
			StringBuilder obj = new StringBuilder();
			if(!Modifier.isStatic(mod)){
				obj.append("((");
				obj.append(classSimpleName);
				obj.append(")obj).");
			}
			String retStatement = "return ";
			if(returnType.equals(Void.TYPE.getName())){
				retStatement = "";
			}
			line(result,"if(signature.equals(\"",signature,
					"\"))",retStatement,obj.toString(),
					prepareMethodCall(signature,"parameters"),";");
		}
		result.add("return null;");
		return result;
	}
	
	private void line(List<String> result,String...parts){
		if(parts.length==0){
			return;
		}
		if(parts.length==1){
			result.add(parts[0]);
			return;
		}
		StringBuilder builder = new StringBuilder();
		for(String part : parts){
			builder.append(part);
		}
		result.add(builder.toString());
	}
	
	private void method(JavaModifier mod, String returnType, String name,
			List<String> body,String...params) {
		result.append('\n');
		indent().indent();		
		modifiers(mod);
		result.append(' ');
		result.append(returnType);
		result.append(' ');
		result.append(name);
		result.append('(');
		for(int i=0;i<params.length;i++){
			if(i>0)result.append(',');
			result.append(params[i]);
		}
		result.append(')');
		result.append('{');
		result.append('\n');
		for(String line : body){
			indent().indent().indent();
			result.append(line);
			result.append('\n');
		}
		indent().indent();
		result.append('}');
		result.append('\n');
	}
	
	private void header() {
		modifiers(JavaModifier.PUBLIC,JavaModifier.STATIC,JavaModifier.FINAL);
		result.append(' ');
		result.append(ClassDelegate.class.getName());
		result.append('<');
		result.append(classSimpleName);
		result.append("> $class = \n");
		indent();
		result.append("new ");
		result.append(ClassDelegate.class.getName());
		result.append('<');
		result.append(classSimpleName);
		result.append(">(");
		result.append(classSimpleName);
		result.append(".class) {");
	}
	
	private void modifiers(JavaModifier...modifiers) {
		boolean space = false;
		for(JavaModifier mod : modifiers){
			if(mod.equals(JavaModifier.DEFAULT))continue;
			if(space)result.append(' ');
			else space = true;
			result.append(mod.name().toLowerCase());
		}
	}
	
	private ClassDelegateBuilder indent() {
		result.append('\t');
		return this;
	}
}
